#! c:\python27\python.exe

from Tkinter import *
import tkMessageBox
from collections import deque
import sys
from random import randint

directions = {'up' : (0, -1), 'down': (0, 1), 'left': (-1, 0), 'right': (1, 0)}

class StatusBar(Frame):
	def __init__(self, parent):
		Frame.__init__(self, parent)
		self.label = Label(self, relief = SUNKEN, anchor = W)
		self.label.pack(fill = X)
	
	def set(self, format, *args):
		self.label.config(text = format % args)
		self.label.update_idletasks()
	
	def clear(self):
		self.label.config(text = "")
		self.label.update_idletasks()

class Block(object):
	def __init__(self, id, (x, y)):
		self.id = id
		self.x = x
		self.y = y

	def coord(self):
		return (self.x, self.y)

class Snake(object):
	def __init__(self, board, blocks = [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0)], direction = directions['right'], color = 'red'):
		self.queue = deque()
		self.blocks = set()
		self.color = color
		for block in blocks:
			id = board.add_block(block, color)
			self.queue.append(Block(id, block))
			self.blocks.add(block)

		self.board = board
		self.direction = direction
		self.length = len(blocks)

	def next_block(self):
		head = self.queue[self.length - 1]
		hx, hy = head.coord()
		dx, dy = self.direction

		hx += dx
		hy += dy

		return (hx, hy)

	def setdir(self, newdir):
		olddir = self.direction
		self.direction = newdir
		hx, hy = self.next_block()
		previous = self.queue[self.length - 2]
		if ((hx, hy) == previous.coord()):
			self.direction = olddir

	def onsnake(self, pos):
		return pos in self.blocks

	def move(self, expand = False):
		hx, hy = self.next_block()
		print 'snake is moving to %s!' % (repr((hx, hy)))
		sys.stdout.flush()

		if (self.onsnake((hx, hy))):
			return False
		if (not self.board.onboard(hx, hy)):
			return False

		if not expand:
			tail = self.queue.popleft()
			print 'deleting block'
			self.board.delete_block(tail.id)
			self.blocks.remove(tail.coord())
			self.length -= 1

		print 'adding block %s' % (repr((hx, hy)))
		id = self.board.add_block((hx, hy), self.color)
		newblock = Block(id, (hx, hy))
		self.blocks.add((hx, hy))
		self.queue.append(newblock)
		self.length += 1

		return True

class Board(Frame):
	def __init__(self, parent, max_x = 20, max_y = 20, scale = 20, offset = 3):
		Frame.__init__(self, parent)
		self.canvas = Canvas(parent, height = (max_y * scale) + offset, width = (max_x * scale) + offset)

		self.canvas.pack()

		self.max_x = max_x
		self.max_y = max_y
		self.scale = scale
		self.offset = offset
		self.parent = parent

	def add_block(self, (x, y), color):
		rx = (x * self.scale) + self.offset
		ry = (y * self.scale) + self.offset

		return self.canvas.create_rectangle(rx, ry, rx + self.scale, ry + self.scale, fill = color)

	def onboard(self, x, y):
		return x >= 0 and x < self.max_x and y >= 0 and y < self.max_y

	def move_block(self, id, coord):
		"""
			notice here coord is relative coordinates. 
		"""
		x, y = coord
		self.canvas.move(id, x * self.scale, y * self.scale)

	def delete_block(self, id):
		self.canvas.delete(id)

class GameController(object):
	def __init__(self, parent):
		self.parent = parent 
		self.status_bar = StatusBar(parent)
		self.status_bar.pack(side = TOP, fill = X)
		self.status_bar.set('Score: %-7d' % (0))
		self.score = 0

		self.board = Board(parent)
		self.board.pack(side = BOTTOM)

		self.snake = Snake(self.board)

		self.delay = 200

		self.parent.bind("<Left>", self.left_callback)
		self.parent.bind("<Right>", self.right_callback)
		self.parent.bind("<Up>", self.up_callback)
		self.parent.bind("<Down>", self.down_callback)

		self.place_food()

		self.parent.after(self.delay, self.move_forward)

	def inc_score(self, delta):
		self.score += delta
		self.status_bar.set('Score: %-7d' % (self.score))
	
	def left_callback(self, event):
		self.snake.setdir(directions['left'])

	def right_callback(self, event):
		self.snake.setdir(directions['right'])

	def up_callback(self, event):
		self.snake.setdir(directions['up'])

	def down_callback(self, event):
		self.snake.setdir(directions['down'])

	def place_food(self):
		while True:
			x = randint(0, self.board.max_x - 1)
			y = randint(0, self.board.max_y - 1)
			if (not self.snake.onsnake((x, y))):
				break
		id = self.board.add_block((x, y), 'blue')
		self.food = Block(id, (x, y))

	def move_forward(self):
		expand = False
		if (self.snake.next_block() == self.food.coord()):
			expand = True
		if (self.snake.move(expand)):
			print 'register again'
			self.parent.after(self.delay, self.move_forward)
		else:
			print 'snake fails to move'
			sys.stdout.flush()
			tkMessageBox.showwarning(title = 'Game Over!', message = 'Press OK to exit.', parent = self.parent)
			self.parent.destroy()
			sys.exit(0)

		if (expand):
			self.board.delete_block(self.food.id)
			self.place_food()
			self.inc_score(10)

if __name__ == '__main__':
	root = Tk()
	root.title('Blockade')
	theGame = GameController(root)
	root.mainloop()
